package com.sinai.mshab.client.ui.aggregate;

import java.util.ArrayList;
import java.util.Date;

import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.sinai.mshab.client.util.Answer;
import com.sinai.mshab.client.util.AnswerCollection;
import com.sinai.mshab.client.util.Util;
import com.sinai.mshab.client.ui.FormComponent;

/**
 * The <code>HorizontalComponentList</code> is a container which can holds
 * other components and displays them in horizontally together.
 * 
 * @author Serge Vesselov, Arthur Kalmenson
 */
public class HorizontalComponentList extends AggregateFormComponent implements
		DataReceiver {

	/** This answer collections keeps track of all answers sent to this hpanel */
	private static AnswerCollection answer_collection = new AnswerCollection();

	/**
	 * The horizontal panel that contains the widgets of this
	 * HorizontalComponentList.
	 */
	private HorizontalPanel horiz_panel;

	/**
	 * An array list of all the components in this
	 */
	private ArrayList components;

	private ArrayList notifyingComponents;

	/**
	 * This boolean specifies that this widget should collect the info of it's
	 * subwidgets instead of the subwidgets reporting to FormData.
	 */
	private boolean collect_sub_info = false;

	/**
	 * Creates a new <code>HorizontalComponentList</code> with a
	 * <code>null</code> table name.
	 */
	public HorizontalComponentList() {
		initializeHorizontalComponentList(null);
	}

	private void initializeHorizontalComponentList(String tableName) {
		this.tableName = tableName;
		horiz_panel = new HorizontalPanel();
		components = new ArrayList();
		notifyingComponents = new ArrayList();
		horiz_panel.setStyleName("horizontal_component");
		Date d = new Date();
		setId(d.getTime());
		initWidget(horiz_panel);
	}

	/**
	 * Adds {@link FormComponent}, <code>component</code> to this
	 * <code>HorizontalComponentList</code>.
	 * 
	 * @param component
	 *            the component to add to this
	 *            <code>HorizontalComponentList</code>
	 */
	public void add(FormComponent component) {
		component.setData_receiver(this);
		horiz_panel.add(component);
		components.add(component);
	}

	/**
	 * Adds {@link Widget}, <code>component</code> to this
	 * <code>HorizontalComponentList</code>.
	 * 
	 * @param component
	 *            the component to add to this
	 *            <code>HorizontalComponentList</code>
	 */
	public void add(Widget component) {
		horiz_panel.add(component);
	}

	/**
	 * Adds {@link FormComponent}, <code>component</code> to this
	 * <code>HorizontalComponentList</code>.
	 * 
	 * @param component
	 *            the component to add to this
	 *            <code>HorizontalComponentList</code>
	 * @param index
	 *            the index at which to insert the widget
	 */
	public void add(FormComponent component, int index) {
		component.setData_receiver(this);
		horiz_panel.insert(component, index);
		components.add(component);
	}

	/**
	 * Adds {@link Widget}, <code>component</code> to this
	 * <code>HorizontalComponentList</code>.
	 * 
	 * @param component
	 *            the component to add to this
	 *            <code>HorizontalComponentList</code>
	 * @param index
	 *            the index at which to insert the widget
	 */
	public void add(Widget component, int index) {
		horiz_panel.insert(component, index);
	}

	/**
	 * Add a componenet which will notify the horizontal panel. NOTE: the value
	 * of this component WILL NOT be aggregated into the final answer
	 * 
	 * @param component
	 *            the component which will notify this panel
	 */
	public void addNotifyingComponent(FormComponent component) {
		horiz_panel.add(component);
		component.addObserver(this);
		notifyingComponents.add(component);
	}

	/**
	 * Removes a form component from the panel.
	 * 
	 * @param widget
	 *            the widget to remove.
	 */
	public void remove(FormComponent widget) {
		components.remove(widget);
		horiz_panel.remove(widget);
	}

	/**
	 * Removes the widget at index, index.
	 * 
	 * @param index
	 *            the index of the widget that needs to be removed.
	 */
	public void remove(int index) {

		// if it's a FormComponent, we want to remove it from the components
		// list as well.
		if (horiz_panel.getWidget(index) instanceof FormComponent) {
			FormComponent c = (FormComponent) horiz_panel.getWidget(index);
			for (int i = 0; i < components.size(); i++) {
				if (components.get(i) == c) {
					components.remove(i);
				}
			}
		}

		// remove it from the horizontal panel.
		horiz_panel.remove(index);
	}

	/**
	 * We received a notification, probably from a "another instance of?"
	 * component in this panel. Thus we notify any above panels that may contain
	 * this panel.
	 * 
	 * @param event
	 *            the message being passed by the observable.
	 */
	public void receiveNotification(FormComponent who) {
		notifyObservers();
	}

	/**
	 * Returns the aggregate answer for in this
	 * <code>HorizontalComponentList</code>.
	 */
	public void sendAnswer(DataReceiver receiver) {

		answer_collection = new AnswerCollection();

		// tell all subwidgets to send their answers to this panel.
		for (int i = 0; i < components.size(); i++) {
			FormComponent current = (FormComponent) (components.get(i));

			// hpanel will collect and pack info only if it's in a vpanel.
			if (this.getParent() instanceof VerticalComponentList
					|| this.getParent().getParent() instanceof VerticalComponentList
					|| isCollect_sub_info()) {
				current.sendAnswer(this);
			} else {
				current.sendAnswer(receiver);
			}
		}

		// now that all components sent their answers, we can pack our results
		answer_collection.pack();

		// if more than one table name, then all answers are mergable
		boolean mergable = answer_collection.size() > 1;

		// setting mergable state of answers and shipping the answers off
		for (int i = 0; i < answer_collection.size(); i++) {
			Answer a = answer_collection.get(i);
			a.setMergeable(mergable);

			// adding the where clause to non-mergables.
			if (!mergable) {
				System.out.println("Adding where IDs Table is "
						+ a.getTableName());
				a.addWhereClause("id", "" + getId());
			}
			// sending off this answer to our data receiver.
			receiver.sendData(a);
		}
	}

	public Object clone() {
		FormComponent current, cloned;
		HorizontalComponentList h = new HorizontalComponentList();
		
		// pass down information of forced mergability
		h.setCollect_sub_info(isCollect_sub_info());

		// pass down css tag
		h.setStyleName(this.getStyleName());
		
		// appending all the widgets to the new panel
		for (int i = 0; i < horiz_panel.getWidgetCount(); i++) {

			if (horiz_panel.getWidget(i) instanceof FormComponent) {
				current = (FormComponent) horiz_panel.getWidget(i);
				cloned = (FormComponent) current.clone();

				// setting up the observer relationships
				if (notifyingComponents.contains(current)) {
					h.addNotifyingComponent(cloned);
				} else {
					h.add(cloned);
				}
			}
		}
		
		// removing all notifying components since they will be replicated
		// in the cloned widget.
		for (int i = 0; i < this.notifyingComponents.size(); i++) {
			FormComponent notif = (FormComponent) notifyingComponents.get(i);
			this.remove(notif);
		}
		
		return h;
	}

	/**
	 * Populate this widget with FormData using an Answer object
	 * 
	 * @param answer
	 *            the answer object containing the data.
	 */
	public boolean populate(Answer answer) {

		boolean successfully_populated = false;

		// if answer is not null, use it to populate subcomponents.
		if (answer != null) {
			String id = answer.get("id");

			// if the answer object has an id, set our id to it.
			if (id != null) {
				this.setId(Double.parseDouble(id));
			}

			// telling sub components to populate themselves.
			for (int i = 0; i < components.size(); i++) {
				boolean success = ((FormComponent) components.get(i))
						.populate(answer);
				if (success) {
					successfully_populated = true;
				}
			}
		}
		// if the answer object is null, let the widgets populate themselves
		else {
			// telling sub components to populate themselves.
			for (int i = 0; i < components.size(); i++) {
				boolean success = ((FormComponent) components.get(i))
						.populate();
				if (success) {
					successfully_populated = true;
				}
			}
		}
		return successfully_populated;
	}

	/**
	 * Populate this widget with FormData
	 */
	public boolean populate() {
		Answer a = FormData.getData(tableName);
		return populate(a);
	}

	public void sendData(Answer answer) {
		answer_collection.add(answer);
	}

	/**
	 * @param collect_sub_info
	 *            the collect_sub_info to set
	 */
	public void setCollect_sub_info(boolean collect_sub_info) {
		this.collect_sub_info = collect_sub_info;
	}

	/**
	 * @return the collect_sub_info
	 */
	public boolean isCollect_sub_info() {
		return collect_sub_info;
	}

	/**
	 * @see FormComponent#isEmpty()
	 */
	public boolean isEmpty() {

		boolean empty = true;

		// go through all the FormComponents and find if any are not empty
		for (int i = 0; i < horiz_panel.getWidgetCount(); i++) {
			Widget w = horiz_panel.getWidget(i);
			if (w instanceof FormComponent) {
				FormComponent component = (FormComponent) w;
				if (!component.isEmpty()) {
					empty = false;
					break;
				}
			}
		}
		return empty;
	}

	/**
	 * Returns the number of widgets in this HorizontalComponentList.
	 * 
	 * @return the number of widgets in this HorizontalComponentList.
	 */
	public int getWidgetCount() {
		return horiz_panel.getWidgetCount();
	}

	/**
	 * Returns the {@link Widget} in this HorizontalComponentList located at
	 * index.
	 * 
	 * @param index
	 *            the index of the {@link Widget} we're looking for.
	 * @return the {@link Widget} and index, index.
	 */
	public Widget getWidget(int index) {
		return horiz_panel.getWidget(index);
	}

	public VerticalPanel toReport() {
		HorizontalPanel printableVersion = new HorizontalPanel();

		for (int i = 0; i < horiz_panel.getWidgetCount(); i++) {

			Widget widget = horiz_panel.getWidget(i);

			/*
			 * if the widget we're looking at is a BasicFormComponent or an
			 * AggregateFormComponent, we want to call their toPrint() and add
			 * it. However, if it's a regular widget, we still want to add it in
			 * order to maintain the look of the original form.
			 */
			if (widget != null && widget instanceof FormComponent) {
				printableVersion.add(Util.callToPrint((FormComponent) widget));
			} else if (widget != null) {
				printableVersion.add(widget);
			}
		}

		VerticalPanel vertical = new VerticalPanel();
		vertical.add(printableVersion);

		return vertical;
	}
}
